use qjs_sys as q;
use std::ffi::{CString,CStr};
use std::os::raw::{c_void,c_char};
use std::collections::{HashSet};
use crate::{RawJsValue};


pub struct JSRuntime {
    c_runtime:*mut q::JSRuntime,
    //rt_info:CString,
    data:Box<RuntimeData>
}

#[repr(C)]
pub struct RuntimeData {
  pub c_modules:HashSet<String>,
  pub c_init_modules:HashSet<String>
}

impl Default for RuntimeData {
  fn default() -> Self {
    RuntimeData {
      c_modules:HashSet::default(),
      c_init_modules:HashSet::default()
    }
  }
}

impl JSRuntime {
    pub fn new() -> Result<Self,String> {
       let rt = unsafe { q::JS_NewRuntime() };
       if rt.is_null() {
         Err(String::from("create JSRuntime fail"))
       } else {
         let mut rt = JSRuntime { 
           c_runtime : rt , 
           //rt_info:CString::default(),
           data:Box::new(RuntimeData::default())
         };
         rt.set_default_module_loader();
         Ok(rt)
       }       
    }

    pub fn c_rt(&self) -> *mut q::JSRuntime {
      self.c_runtime
    }

    /*
    pub fn set_memory_limit (&mut self,limit:usize) {
       unsafe { q::JS_SetMemoryLimit(self.c_runtime, limit) }
    }

    pub fn set_runtime_info(&mut self,_str:&str) {
      self.rt_info = CString::new(_str).unwrap();
      unsafe { q::JS_SetRuntimeInfo(self.c_runtime,self.rt_info.as_ptr()) };
    }

    pub fn set_gc_threshold(&mut self,gc_threshold:usize) {
      unsafe {q::JS_SetGCThreshold(self.c_runtime,gc_threshold); }
    }

    pub fn run_gc(&mut self) {
      unsafe {q::JS_RunGC(self.c_runtime); }
    }

    
    pub fn is_in_gc_sweep(&self) -> bool {
      unsafe { q::JS_IsInGCSweep(self.c_runtime) > 0 }
    }

    pub fn memory_usage(&self) -> JSMemoryUsage {
     let mut mem:JSMemoryUsage = Default::default();
     let ptr:*mut JSMemoryUsage =  &mut mem as *mut JSMemoryUsage;
     unsafe {q::JS_ComputeMemoryUsage(self.c_runtime,ptr as *mut _); };
     mem
    }
    */

    pub fn set_module_loader(&mut self,normalize_func:q::JSModuleNormalizeFunc,loader_func:q::JSModuleLoaderFunc) {
      unsafe {
        let ptr = self.data.as_mut() as *mut RuntimeData;
        q::JS_SetModuleLoaderFunc(self.c_rt(),normalize_func,loader_func,std::mem::transmute(ptr));
      };
    }

    pub fn set_default_module_loader(&mut self) {
      self.set_module_loader(None,Some(default_module_loader));
    }
}

unsafe extern "C" fn default_module_loader(ctx: *mut q::JSContext,module_name: *const c_char,opaque:*mut c_void) -> *mut q::JSModuleDef {
  
  let cstr = CString::from(CStr::from_ptr(module_name));
  let mstr = cstr.into_string().unwrap();
  let module_path = std::path::Path::new(&mstr);
  let _hs:&mut RuntimeData = std::mem::transmute(opaque);
  let ext = module_path.extension();
  let is_js_file = ext.is_some() && ext.unwrap() == "js";
  if is_js_file {
    //println!("load js module:{:?}",&module_path);
    let may_js_bytes = std::fs::read(module_path);
    if may_js_bytes.is_err() {
      eprintln!("not found:{:?}",std::env::current_dir().unwrap());
      q::JS_ThrowReferenceError(ctx, c_str!("could not load module filename '%s'").as_ptr(),module_name);
      return std::ptr::null_mut()
    } else {
       let mut js_string = String::from_utf8(may_js_bytes.unwrap()).unwrap();

       js_string.insert_str(0,crate::INLINE_CODE);
       let c_string = CString::new(js_string.as_str()).unwrap();
       //println!("eval");
       //dbg!(&js_string);
       //dbg!(&mstr);
       let func_val:q::JSValue = q::JS_Eval(ctx,c_string.as_ptr(), js_string.len(), module_name,
                           (q::JS_EVAL_TYPE_MODULE | q::JS_EVAL_FLAG_COMPILE_ONLY) as i32);
       let ret_val = RawJsValue(func_val);
       if ret_val.is_exception() {
         dbg!("is_exception");
         return std::ptr::null_mut()
       } 
       //dbg!(ret_val.tag());
       //println!("load succ");
       return func_val.u.ptr as *mut q::JSModuleDef;
    }
  }
 
  return std::ptr::null_mut()
}

impl Drop for JSRuntime  {
   fn drop(&mut self) {
      println!("drop JSRuntime");
      unsafe { q::JS_FreeRuntime(self.c_runtime) };
    }
}


#[repr(C)]
#[derive(Default,Debug, Copy, Clone)]
pub struct JSMemoryUsage {
    pub malloc_size: i64,
    pub malloc_limit: i64,
    pub memory_used_size: i64,
    pub malloc_count: i64,
    pub memory_used_count: i64,
    pub atom_count: i64,
    pub atom_size: i64,
    pub str_count: i64,
    pub str_size: i64,
    pub obj_count: i64,
    pub obj_size: i64,
    pub prop_count: i64,
    pub prop_size: i64,
    pub shape_count: i64,
    pub shape_size: i64,
    pub js_func_count: i64,
    pub js_func_size: i64,
    pub js_func_code_size: i64,
    pub js_func_pc2line_count: i64,
    pub js_func_pc2line_size: i64,
    pub c_func_count: i64,
    pub array_count: i64,
    pub fast_array_count: i64,
    pub fast_array_elements: i64,
    pub binary_object_count: i64,
    pub binary_object_size: i64,
}

impl std::fmt::Display for JSMemoryUsage {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
       write!(f, concat!("malloc_size = {}\nmalloc_limit = {}\nmemory_used_size = {}\nmalloc_count = {}\nmemory_used_count = {}\natom_count = {}\natom_size = {}\n",
                         "str_count = {}\nstr_size = {}\nobj_count = {}\nobj_size = {}\nprop_count = {}\nprop_size = {}\nshape_count = {}\n",
                         "shape_size = {}\njs_func_count = {}\njs_func_size = {}\njs_func_code_size = {}\njs_func_pc2line_count = {}\njs_func_pc2line_size = {}\n",
                         "c_func_count = {}\narray_count = {}\nfast_array_count = {}\nfast_array_elements = {}\nbinary_object_count = {}\nbinary_object_size = {}\n"),
       self.malloc_size, self.malloc_limit,self.memory_used_size,self.malloc_count,self.memory_used_count,
       self.atom_count,self.atom_size,self.str_count,self.str_size,self.obj_count,self.obj_size,self.prop_count,
       self.prop_size,self.shape_count,self.shape_size,self.js_func_count,self.js_func_size,self.js_func_code_size,
       self.js_func_pc2line_count,self.js_func_pc2line_size,self.c_func_count,self.array_count,self.fast_array_count,
       self.fast_array_elements,self.binary_object_count,self.binary_object_size)
   }
}